package com.github.zyt.uploader.controller;

import com.github.zyt.uploader.constant.HttpStatus;
import com.github.zyt.uploader.domain.AjaxResult;
import com.github.zyt.uploader.domain.FileChunk;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.io.*;
import java.util.*;

/**
 * @author zh_yingtao
 * @date 2023/7/6 10:53
 */
@RestController
@RequestMapping("/file")
@Slf4j
@CrossOrigin(value = "*")
public class FileUploadController {

    /**
     * 默认分片20MB
     */
    @Value("${upload.chunkSize:20971520}")
    private int defaultChunkSize;

    /**
     * 上传路径
     */
    @Value("${upload.filePath:D://uploader}")
    private String uploadPath;

    /**
     * Get方法：校验分片信息
     *
     * @param fileChunk 分片信息
     * @return AjaxResult
     */
    @GetMapping("/upload")
    public AjaxResult checkChunk(FileChunk fileChunk) {
        log.info("校验分片——开始:{}", fileChunk.toString());
        //查询分片是否上传
        File checkChunk = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier() + File.separator + fileChunk.getChunkNumber());
        Map<String, Object> map = new HashMap<>();
        //如果不存在返回
        if (!checkChunk.exists()) {
            map.put("uploaded", false);
            return AjaxResult.success(map);
        } else {
            //如果存在，查询当前文件已经上传了哪些分片了
            File file = new File(uploadPath + File.separator + fileChunk.getIdentifier());
            final File[] files = file.listFiles();
            List<Integer> uploadedChunksList = new ArrayList<>();
            if (files != null && files.length > 0) {
                for (File chunk : files) {
                    //如果分片大小相等，且分片不等于最后一片，返回已经上传的分片列表
                    if (chunk.length() == defaultChunkSize && (Integer.parseInt(chunk.getName()) != fileChunk.getTotalChunks())) {
                        uploadedChunksList.add(Integer.parseInt(chunk.getName()));
                    }
                }
            }
            Integer[] uploadedChunks = new Integer[0];
            if (!uploadedChunksList.isEmpty()) {
                uploadedChunks = (Integer[]) uploadedChunksList.toArray();
            }
            map.put("uploadedChunks", uploadedChunks);
            log.info("校验分片——结束:{}", fileChunk.toString());
            return new AjaxResult(HttpStatus.SUCCESS, "上传成功", map);
        }
    }

    /**
     * Post方法：分片上传
     *
     * @param fileChunk 分片
     * @return AjaxResult
     */
    @PostMapping("/upload")
    public AjaxResult uploadChunk(FileChunk fileChunk) {
        log.info("上传分片——开始:{}", fileChunk.toString());
        if (fileChunk.getFile().isEmpty()) {
            log.error("上传文件不存在！");
            throw new RuntimeException("上传文件不存在！");
        }
        File chunkPath;
        //判断是否是文件夹
        if (fileChunk.getRelativePath().contains("/")) {
            final String parentPath = fileChunk.getRelativePath().substring(0, fileChunk.getRelativePath().lastIndexOf("/"));
            chunkPath = new File(uploadPath + File.separator + "temp" + File.separator + parentPath + File.separator + fileChunk.getIdentifier());
        } else {
            chunkPath = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
        }

        if (!chunkPath.exists()) {
            final boolean flag = chunkPath.mkdirs();
            if (!flag) {
                log.error("创建目录失败！");
                return new AjaxResult(HttpStatus.NOT_MODIFIED, "上传失败");
            }
        }
        RandomAccessFile raFile = null;
        BufferedInputStream inputStream = null;
        try {
            File chuckFile = new File(chunkPath, String.valueOf(fileChunk.getChunkNumber()));
            raFile = new RandomAccessFile(chuckFile, "rw");
            raFile.seek(raFile.length());
            inputStream = new BufferedInputStream(fileChunk.getFile().getInputStream());
            byte[] buf = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buf)) != -1) {
                raFile.write(buf, 0, length);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (raFile != null) {
                try {
                    raFile.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        log.info("上传分片——结束:{}", fileChunk.toString());
        return new AjaxResult(HttpStatus.SUCCESS, "上传成功");
    }

    /**
     * 合并文件
     *
     * @param fileChunk 分片信息
     * @return AjaxResult
     */
    @PostMapping("/merge")
    public AjaxResult merge(@RequestBody FileChunk fileChunk) {
        log.info("合并文件——开始：{}", fileChunk.toString());
        //分片文件临时目录
        File tempPath = new File(uploadPath + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
        //真实上传路径
        File realPath = new File(uploadPath + File.separator + "real");
        if (!realPath.exists()) {
            realPath.mkdirs();
        }
        File realFile = new File(uploadPath + File.separator + "real" + File.separator + fileChunk.getFilename());
        //判断目标文件是否存在，若存在删除
//        if (realFile.exists()) {
//            realFile.delete();
//        } else {
//            try {
//                realFile.createNewFile();
//            } catch (IOException e) {
//                throw new RuntimeException(e);
//            }
//        }
        // 文件追加写入
        FileOutputStream os;
        try {

            os = new FileOutputStream(realFile, true);
            if (tempPath.exists()) {
                //获取临时目录下的所有文件
                File[] tempFiles = tempPath.listFiles();
                //按名称排序
                Arrays.sort(tempFiles, (o1, o2) -> {
                    if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
                        return -1;
                    }
                    if (Integer.parseInt(o1.getName()) == Integer.parseInt(o2.getName())) {
                        return 0;
                    }
                    return 1;
                });
                //每次读取10MB大小，字节读取
                byte[] bytes = new byte[10 * 1024 * 1024];
                int len;

                for (int i = 0; i < tempFiles.length; i++) {
                    FileInputStream fis = new FileInputStream(tempFiles[i]);
                    while ((len = fis.read(bytes)) != -1) {
                        os.write(bytes, 0, len);
                    }
                    fis.close();
                    //删除分片
                    tempFiles[i].delete();
                }
                os.close();
                //删除临时目录
                if (tempPath.isDirectory() && tempPath.exists()) {
                    System.gc(); // 回收资源
                    tempPath.delete();
                }

            }
        } catch (Exception e) {
            log.error("文件合并——失败 " + e.getMessage());
            return AjaxResult.error("文件合并失败：" + e.getMessage());
        }
        log.info("合并文件——结束：{}", fileChunk.toString());
        return AjaxResult.success("合并成功");
    }
}
